using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;

namespace LegoBotPrimaAbstractions.Unity
{
    public class UnityContainerInfo : UnityContainerExtension
    {
        private readonly List<RegisterEventArgs> _registrations = new List<RegisterEventArgs>();
        private readonly HashSet<string> _registrationsIndex = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
        private readonly Dictionary<string, RegisterEventArgs> _typesByName = new Dictionary<string, RegisterEventArgs>(StringComparer.CurrentCultureIgnoreCase);
        private readonly Dictionary<Type, RegisterEventArgs> _typesWithoutName = new Dictionary<Type, RegisterEventArgs>();

        private readonly Dictionary<Type, RegisterEventArgs> _concreteTypesLastInfo = new Dictionary<Type, RegisterEventArgs>();
        private readonly Dictionary<Type, RegisterEventArgs> _abstractTypesLastInfo = new Dictionary<Type, RegisterEventArgs>();

        protected override void Initialize()
        {
            Context.Registering += Context_Registering;
        }

        private void Context_Registering(object sender, RegisterEventArgs e)
        {
            if (e.TypeTo != null) _concreteTypesLastInfo[e.TypeTo] = e;
            if (e.TypeFrom != null) _abstractTypesLastInfo[e.TypeFrom] = e;

            var str = RegisterEventArgsToString(e);
            if (!this._registrationsIndex.Contains(str))
            {
                _registrationsIndex.Add(str);
                _registrations.Add(e);
                if (e.TypeFrom != null)
                {
                    if (e.Name == null)
                    {
                        _typesWithoutName[e.TypeFrom] = e;
                    }
                    else
                    {
                        _typesByName[TypeAndName(e.TypeFrom, e.Name)] = e;
                    }
                }
            }
        }

        private static string RegisterEventArgsToString(RegisterEventArgs e)
        {
            var sb = new StringBuilder();
            sb.Append(TypeToString(e.TypeFrom));
            sb.Append("_");
            sb.Append(TypeToString(e.TypeTo));
            sb.Append("_");
            sb.Append(e.Name ?? "NULL");
            sb.Append("_");
            sb.Append(ObjectTypeToString(e.LifetimeManager));
            return sb.ToString();
        }

        static string TypeAndName(Type type, string name)
        {
            return String.Concat(type.FullName, "_", name);
        }

        static string ObjectTypeToString(object e)
        {
            return e == null ? "NULL" : e.GetType().FullName;
        }
        static string TypeToString(Type t)
        {
            return t == null ? "NULL" : t.FullName;
        }



        #region High Performance methods


        public bool HasTypeWithName<TFrom>(string name)
        {
            return HasTypeWithName(typeof(TFrom), name);
        }
        public bool HasTypeWithName(Type from, string name)
        {
            return _typesByName.ContainsKey(TypeAndName(from, name));
        }
        public bool HasTypeWithoutName<TFrom>()
        {
            return HasTypeWithoutName(typeof(TFrom));
        }
        public bool HasTypeWithoutName(Type from)
        {
            return _typesWithoutName.ContainsKey(from);
        }


        public Type GetTypeWithName<TFrom>(string name)
        {
            return GetTypeWithName(typeof(TFrom), name);
        }
        public Type GetTypeWithName(Type from, string name)
        {
            return _typesByName[TypeAndName(from, name)].TypeTo;
        }
        public Type GetTypeWithoutName<TFrom>()
        {
            return GetTypeWithoutName(typeof(TFrom));
        }
        public Type GetTypeWithoutName(Type from)
        {
            return _typesWithoutName[from].TypeTo;
        }



        public Type GetTypeWithNameOrNull<TFrom>(string name)
        {
            return GetTypeWithNameOrNull(typeof(TFrom), name);
        }
        public Type GetTypeWithNameOrNull(Type from, string name)
        {
            var key = TypeAndName(from, name);
            return _typesByName.ContainsKey(key)
                        ? _typesByName[key].TypeTo
                        : null;
        }
        public Type GetTypeWithoutNameOrNull<TFrom>()
        {
            return GetTypeWithoutNameOrNull(typeof(TFrom));
        }
        public Type GetTypeWithoutNameOrNull(Type from)
        {
            return _typesWithoutName.ContainsKey(from)
                        ? _typesWithoutName[from].TypeTo
                        : null;
        }



        public bool TryGetTypeWithName<TFrom>(string name, out Type to)
        {
            return TryGetTypeWithName(typeof(TFrom), name, out to);
        }
        public bool TryGetTypeWithName(Type from, string name, out Type to)
        {
            RegisterEventArgs arg;
            if (_typesByName.TryGetValue(TypeAndName(from, name), out arg))
            {
                to = arg.TypeTo;
                return true;
            }
            to = null;
            return false;
        }
        public bool TryGetTypeWithoutName<TFrom>(out Type to)
        {
            return TryGetTypeWithoutName(typeof(TFrom), out to);
        }
        public bool TryGetTypeWithoutName(Type from, out Type to)
        {
            RegisterEventArgs arg;
            if (_typesWithoutName.TryGetValue(from, out arg))
            {
                to = arg.TypeTo;
                return true;
            }
            to = null;
            return false;
        }




        #endregion


        [Obsolete("Please use GetTypeWithoutName")]
        public Type GetConcreteType(Type abstractType)
        {
            return this.GetConcreteType(abstractType, null);
        }
        public Type GetConcreteType(Type abstractType, Type lifetimeManagerType)
        {
            return this.GetConcreteType(abstractType, lifetimeManagerType, null);
        }
        public Type GetConcreteType(Type abstractType, Type lifetimeManagerType, string name)
        {
            var args = _registrations.FirstOrDefault(
                        e =>
                            e.TypeFrom == abstractType &&
                            (lifetimeManagerType == null ||
                                (e.LifetimeManager != null && e.LifetimeManager.GetType() == lifetimeManagerType)) &&
                            (name == null || e.Name == name)
                        );
            return args == null ? null : args.TypeTo;
        }



        public IEnumerable<Type> GetAllConcreteTypes(Type abstractType)
        {
            return this.GetAllConcreteTypes(abstractType, null);
        }
        public IEnumerable<Type> GetAllConcreteTypes(Type abstractType, Type lifetimeManagerType)
        {
            return this.GetAllConcreteTypes(abstractType, lifetimeManagerType, null);
        }
        public IEnumerable<Type> GetAllConcreteTypes(Type abstractType, Type lifetimeManagerType, string name)
        {
            var query = from reg in _registrations
                        where reg.TypeFrom == abstractType &&
                            (lifetimeManagerType == null ||
                                (reg.LifetimeManager != null && reg.LifetimeManager.GetType() == lifetimeManagerType)) &&
                            (name == null || reg.Name == name)
                        select reg.TypeTo;

            return query;
        }



        public IEnumerable<RegisterEventArgs> GetAllConcreteArgs(Type abstractType)
        {
            return this.GetAllConcreteArgs(abstractType, null);
        }
        public IEnumerable<RegisterEventArgs> GetAllConcreteArgs(Type abstractType, Type lifetimeManagerType)
        {
            return this.GetAllConcreteArgs(abstractType, lifetimeManagerType, null);
        }
        public IEnumerable<RegisterEventArgs> GetAllConcreteArgs(Type abstractType, Type lifetimeManagerType, string name)
        {
            var query = from reg in _registrations
                        where reg.TypeFrom == abstractType &&
                            (lifetimeManagerType == null ||
                                (reg.LifetimeManager != null && reg.LifetimeManager.GetType() == lifetimeManagerType)) &&
                            (name == null || reg.Name == name)
                        select reg;

            return query;
        }


        public RegisterEventArgs GetLastInfoByAbstractType(Type abstractType)
        {
            RegisterEventArgs e;
            return _abstractTypesLastInfo.TryGetValue(abstractType, out e) ? e : null;
        }

        public RegisterEventArgs GetLastInfoByConcreteType(Type concreteType)
        {
            RegisterEventArgs e;
            return _concreteTypesLastInfo.TryGetValue(concreteType, out e) ? e : null;
        }


        [Obsolete("Please use HasTypeWithoutName")]
        public bool IsTypeRegistered<TFrom>()
        {
            return IsTypeRegistered(typeof(TFrom), null);
        }
        public bool IsTypeRegistered<TFrom, TTo>()
        {
            return IsTypeRegistered(typeof(TFrom), typeof(TTo));
        }
        [Obsolete("Please use HasTypeWithName")]
        public bool IsTypeRegistered<TFrom>(string name)
        {
            return IsTypeRegistered(typeof(TFrom), null, name);
        }
        public bool IsTypeRegistered<TFrom, TTo>(string name)
        {
            return IsTypeRegistered(typeof(TFrom), typeof(TTo), name);
        }
        public bool IsTypeRegistered(Type typeFrom, Type typeTo)
        {
            return IsTypeRegistered(typeFrom, typeTo, null);
        }
        public bool IsTypeRegistered(Type typeFrom, Type typeTo, string name)
        {
            return _registrations.FirstOrDefault(
                            e =>
                                e.TypeFrom == typeFrom &&
                                (typeTo == null || e.TypeTo == typeTo) &&
                                (name == null || e.Name == name)
                   ) != null;
        }




        public bool IsTypeRegisteredAsSingleton<TFrom>()
        {
            return IsTypeRegisteredAsSingleton(typeof(TFrom), null);
        }
        public bool IsTypeRegisteredAsSingleton<TFrom, TTo>()
        {
            return IsTypeRegisteredAsSingleton(typeof(TFrom), typeof(TTo));
        }
        public bool IsTypeRegisteredAsSingleton<TFrom>(string name)
        {
            return IsTypeRegisteredAsSingleton(typeof(TFrom), null, name);
        }
        public bool IsTypeRegisteredAsSingleton<TFrom, TTo>(string name)
        {
            return IsTypeRegisteredAsSingleton(typeof(TFrom), typeof(TTo), name);
        }
        public bool IsTypeRegisteredAsSingleton(Type typeFrom, Type typeTo)
        {
            return IsTypeRegisteredAsSingleton(typeFrom, typeTo, null);
        }
        public bool IsTypeRegisteredAsSingleton(Type typeFrom, Type typeTo, string name)
        {
            return _registrations.FirstOrDefault(
                            e =>
                                e.TypeFrom == typeFrom &&
                                (typeTo == null || e.TypeTo == typeTo) &&
                                (name == null || e.Name == name) &&
                                typeof(ContainerControlledLifetimeManager).IsInstanceOfType(e.LifetimeManager)
                   ) != null;
        }
    }
}